import * as THREE from '../../../../modules/three/three.module.js'
import {flatten} from "../util/StaticMethods.js";

const expectSpan = 3
const colorPhi = '#009d32'
const colorTheta = '#f332b9'
const mtlPhi = new THREE.MeshBasicMaterial({color: colorPhi, toneMapped: false})
const mtlTheta = new THREE.MeshBasicMaterial({color: colorTheta, toneMapped: false})
const unitZ = new THREE.Vector3(0, 0, 1)
const _v1 = new THREE.Vector3()
const vA = new THREE.Vector3()
const vB = new THREE.Vector3()

export default class IsoLineEntity extends THREE.Group {
  /**
   * @type {IsoLineVO}
   */
  vo
  /**
   * @type {NormalPointIndicator[]}
   */
  points = []
  /**
   * @type {THREE.LineSegments}
   */
  lineSegments
  /**
   * @type {THREE.LineSegments}
   */
  _latestSegment

  constructor(vo) {
    super()
    this.vo = vo
    const posAttr = new THREE.BufferAttribute(new Float32Array([0,0,-0.5, 0,0,0.5]), 3)
    const lineGeo = new THREE.BufferGeometry();
    lineGeo.setAttribute('position', posAttr)
    const mtl = vo.isPhi() ? mtlPhi : mtlTheta
    const segment = new THREE.LineSegments(lineGeo, mtl)
    this.add(segment)
    segment.visible = false
    this._latestSegment = segment
  }
  setPoints(pointIndicators) {
    this.points = this.vo.normalPoints.map(it => pointIndicators.find(indicator => indicator.vo === it))
    return this
  }

  updateVO() {
    this.vo.normalPoints = this.points.map(it => it.vo)
  }
  /**
   * @param {NormalPointIndicator} point
   */
  insertPoint(point) {
    this.points.push(point)
  }
  getExpectedSpan() {
    return this.points.length * expectSpan
  }
  showTips(scene, camera) {
    this.points.forEach((it, index) => {
      it.showTip(scene, camera)
      if (index === 0) {
        it.updateTip()
      } else {
        it.updateTipByColor(this)
      }
    })
  }
  hideTips() {
    this.points.forEach(it => it.hideTip())
  }
  createOrUpdateShape() {
    if (this.points.length < 2) {
      return
    }
    if (this.lineSegments) {
      this.lineSegments.removeFromParent()
    }
    // this.points = this.points.filter(it => it)
    const pts = this.points.map(it => it.vo.position.clone().add(it.vo.normal))
    const lineIndices = []
    for (let i = 0; i < pts.length - 1; i++) {
      lineIndices.push(i, i + 1);
    }
    const posAttr = new THREE.BufferAttribute(flatten(pts), 3)
    const lineGeo = new THREE.BufferGeometry();
    lineGeo.setIndex(lineIndices);
    lineGeo.setAttribute('position', posAttr)
    const mtl = this.vo.isPhi() ? mtlPhi : mtlTheta
    const lineSegments = new THREE.LineSegments(lineGeo, mtl)
    this.add(lineSegments)
    this.lineSegments = lineSegments
  }
  replacePoint(oldPoint, newPoint) {
    const index = this.points.indexOf(oldPoint)
    if (index < 0) {
      return
    }
    this.points[index] = newPoint
  }
  updateShape(pointIndicator) {
    if (this.points.length < 2) {
      return
    }
    const posAttr = this.lineSegments.geometry.getAttribute('position')
    const index = this.points.indexOf(pointIndicator)
    const {x, y, z} = this.points[index].vo.getAbovePos()
    posAttr.setXYZ(index, x, y, z)
    posAttr.needsUpdate = true
  }
  updateEndSegment(pointIndicator) {
    if (this.points.length < 1) {
      return
    }
    const segment = this._latestSegment
    segment.visible = true
    const index = this.points.length - 1
    const a = this.points[index]
    const b = pointIndicator
    vA.copy(a.vo.position).add(a.vo.normal)
    vB.copy(b.vo.position).add(b.vo.normal)
    segment.scale.z = vA.distanceTo(vB)
    segment.position.copy(vA).lerp(vB, 0.5)
    _v1.copy(vB).sub(vA).normalize()
    segment.quaternion.setFromUnitVectors(unitZ, _v1)
  }
  hideEndSegment() {
    this._latestSegment.visible = false
  }
}
